/*
 * Copyright (c) 2001 Boris Pruessmann and others.
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Common Public License v0.5
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v05.html
 * 
 * Contributors:
 *    Boris Pruessmann - Initial implementation.
 */
package net.sourceforge.p4eclipse.ui;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import net.sourceforge.p4eclipse.core.IResourceStateChangeListener;
import net.sourceforge.p4eclipse.core.Policy;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.ILabelDecorator;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.LabelProviderChangedEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Display;

/**
  * XXX TODO: Optimizations ...
  * 
  * @version 	1.0
  * @author		<a href="mailto:bpruessmann@users.sourceforge.net">Boris Pruessmann</a>
  */
public class PerforceDecorator extends LabelProvider implements ILabelDecorator, IResourceStateChangeListener, IDecorationNotifier
{
	public void resourceStateChanged(IResource[] changedResources)
	{
		List resourcesToDecorate = new ArrayList();
		for (int i = 0, length = changedResources.length; i < length; i++)
		{
			if (cache.containsKey(changedResources[i]))
				resourcesToDecorate.add(changedResources[i]);
		}
		
		addResourcesToBeDecorated((IResource[])resourcesToDecorate.toArray(new IResource[0]));		
	}

	private static PerforceDecorator instance;

	private Set decoratorNeedsUpdating = Collections.synchronizedSet(new HashSet());
	private Map cache = Collections.synchronizedMap(new HashMap());

	private Thread decoratorUpdateThread;

	private boolean shutdown = false;

	private OverlayIconCache iconCache = new OverlayIconCache();

	private static class DecoratorOverlayIcon extends OverlayIcon 
	{
		public DecoratorOverlayIcon(Image base, ImageDescriptor[] overlays) 
		{
			super(base, overlays, new Point(base.getBounds().width, base.getBounds().height));
		}
		
		protected void drawOverlays(ImageDescriptor[] overlays) 
		{
			for (int i = overlays.length - 1; i >= 0; --i) 
			{
				ImageData imageData = overlays[i].getImageData();
				drawImage(imageData, 0, 0);
			}
		}
	}


	/**
	  * Constructor for PerforceDecorator.
	  */
	public PerforceDecorator()
	{
		Policy.assertNull(instance);
		instance = this;

		decoratorUpdateThread = new Thread(new PerforceDecorationRunnable(this), "Perforce"); //$NON-NLS-1$
		decoratorUpdateThread.start();
		
		PerforceUIPlugin.getPlugin().addResourceStateChangeListener(this);
	}

	/*
	 * @see ILabelDecorator#decorateImage(Image, Object)
	 */
	public Image decorateImage(Image image, Object element)
	{
		IResource resource = getResource(element);
		if (resource == null || image == null || resource.getType() == IResource.ROOT)
			return image;
		
		PerforceDecoration decoration = (PerforceDecoration)cache.get(resource);
		if (decoration == null)
		{
			addResourcesToBeDecorated(new IResource[] { resource });
			return image;
		}
		else
		{
			List overlays = decoration.getOverlays();
			if (overlays != null)
			{
				return iconCache.getImageFor(new DecoratorOverlayIcon(image, (ImageDescriptor[])overlays.toArray(new ImageDescriptor[overlays.size()])));
			}
		}
		return image;
	}

	/*
	 * @see ILabelDecorator#decorateText(String, Object)
	 */
	public String decorateText(String text, Object element)
	{
		IResource resource = getResource(element);
		if (resource == null || text == null || resource.getType() == IResource.ROOT)
			return text;
		
		PerforceDecoration decoration = (PerforceDecoration)cache.get(resource);
		if (decoration == null)
		{
			addResourcesToBeDecorated(new IResource[] { resource });
			return text;
		}
		else
			return decoration.decorateText(text);

	}

	/*
	 * @see IResourceStateChangeListener#resourceStateChanged(IResource[])
	 */
	
	private IResource getResource(Object object)
	{
		if (object instanceof IResource)
			return (IResource) object;
		else
		if (object instanceof IAdaptable)
			return (IResource) ((IAdaptable) object).getAdapter(IResource.class);
		else
			return null;
	}

	public synchronized void addResourcesToBeDecorated(IResource[] resources)
	{
		if (resources.length > 0)
		{
			for (int i = 0, length = resources.length; i < length; i++)
				decoratorNeedsUpdating.add(resources[i]);
				
			notify();
		}
	}

	private void postLabelEvents(final LabelProviderChangedEvent[] events) 
	{
		// now post the change events to the UI thread
		if (events.length > 0) 
		{
			Display.getDefault().asyncExec(
				new Runnable() 
				{
					public void run() 
					{
						for (int i = 0; i < events.length; i++) 
							fireLabelProviderChanged(events[i]);
					}
				});
		}
	}
	 
	/*
	 * @see IDecorationNotifier#decorated(IResource, PerforceDecoration)
	 */
	public void decorated(IResource[] resources, PerforceDecoration[] decorations)
	{
		List events = new ArrayList();
		if (!shutdown)
		{
			for (int i = 0, length = resources.length; i < length; i++)
			{
				IResource resource = resources[i];
				if (resource.exists())
				{
					cache.put(resource, decorations[i]);
					events.add(new LabelProviderChangedEvent(this, resource));
				}
			}
			
			postLabelEvents((LabelProviderChangedEvent[])events.toArray(new LabelProviderChangedEvent[0]));
		}
	}

	/*
	 * @see IDecorationNotifier#next()
	 */
	public synchronized IResource next()
	{
		try 
		{
			if (shutdown) 
				return null;
			
			if (decoratorNeedsUpdating.isEmpty()) 
				wait();
	
			if (shutdown) 
				return null;

			Iterator it = decoratorNeedsUpdating.iterator();
	
			IResource result = (IResource)it.next();
			it.remove();
	
			return result;
		} 
		catch(InterruptedException e) 
		{
		}		
		
		return null;
	}

	/**
	 * @see IDecorationNotifier#remaining()
	 */
	public int remaining()
	{
		return decoratorNeedsUpdating.size();
	}

	public static void shutdown() 
	{
		if (instance != null)
			instance.dispose();
	}


	private void shutdownAll() 
	{
		shutdown = true;
		
		synchronized (this)
		{
			notifyAll();
		}
		
		try 
		{
			decoratorUpdateThread.join();
		}
		catch (InterruptedException e) 
		{
		}
	}

	public void dispose() 
	{
		super.dispose();

		PerforceUIPlugin.getPlugin().removeResourceStateChangeListener(this);
		
		decoratorNeedsUpdating.clear();
		cache.clear();

		shutdownAll();

		iconCache.disposeAll();		
		instance = null;
	}
}
